'''
dionysus - a web based dialogue management framework
Copyright (C) 2011  Eugeniu Costetchi <eugeniu.costetchi (at) tudor.lu>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

Created on Jan 12, 2011
'''
from DialogueManager.Context import Context, access_context_object
from DialogueManager.Parser import InputTypes, input_compatible
from DialogueManager.utils.utils import trim
from DialogueManager.planning.EventDecorator import Event
from DialogueManager.utils.log_utils import log_debug

#===============================================================================
#Operators 
#===============================================================================
def operand_type(operand=None):
    if operand is not None:
        if isinstance(operand, basestring) and trim(operand.lower(), False).startswith(Context.CTX):
            return Context
    else:
        return None
    return operand.__class__

class Operator(object):
    
    def __init__(self):
        self.result = None
        self._op_sign = self.__class__.__name__
    
    def __eq__(self, other):
        if isinstance(other, Operator):
            return (self.result == other.result)
        else:
            return (self.result == other)
    @Event
    def evaluate(self, context):
        pass
    
class BinaryOperator(Operator):
    def __init__(self, left, right):
        Operator.__init__(self)
        self.__left = left
        self.__right = right
        self.l_value = None
        self.r_value = None
        self.evaluate += self.__eval
        
    def __eval(self, context):
        #print 'Binary operator _eval'
        try:
            if operand_type(self.__left) == Context:
                self.l_value = access_context_object(self.__left, context)
            elif isinstance(self.__left, Operator):
                self.__left.evaluate(context)
                self.l_value = self.__left.result
            else: self.l_value = self.__left
            
            if operand_type(self.__right) == Context:
                self.r_value = access_context_object(self.__right, context)
            elif isinstance(self.__right, Operator):
                self.__right.evaluate(context)
                self.r_value = self.__right.result
            else: self.r_value = self.__right
        except TypeError:
            log_debug('probably self.__left of self.__right is wrong:' + str(self.__left) + ',' + str(self.__right), True) 
        #print 'values ...', self.l_value, self.r_value

    def __str__(self):
        return '' + str(self.__left) + ' ' + self._op_sign + ' ' + str(self.__right)

        
class Eq(BinaryOperator):
    def __init__(self, left, right):
        BinaryOperator.__init__(self, left, right)
        self._op_sign = '=='
        self.evaluate += self.__eq

    def __eq(self, context):   
        #print 'Eq _eq', type(self.l_value),type(self.r_value)      
        self.result = (self.l_value == self.r_value)

class GreaterThan(BinaryOperator):
    def __init__(self, left, right):
        BinaryOperator.__init__(self, left, right)
        self._op_sign = '>'
        self.evaluate += self.__gr

    def __gr(self, context):   
        self.result = (self.l_value > self.r_value)

class LessThan(BinaryOperator):
    
    def __init__(self, left, right):
        BinaryOperator.__init__(self, left, right)
        self._op_sign = '<'
        self.evaluate += self.__lt

    def __lt(self, context):
        self.result = (self.l_value < self.r_value)
    
class In(BinaryOperator):
    ''' test if left exists in collection right'''
    def __init__(self, value, colect):
        BinaryOperator.__init__(self, value, colect)
        self._op_sign = 'in'
        self.evaluate += self.__in

    def __in(self, context):
        try:   
            self.result = (self.l_value in self.r_value)
        except TypeError:
            log_debug('There is an inconsistency in the IN operator', exception=True)
            self.result = False

class And(BinaryOperator):
    '''  represents a logical and between two operators  '''
    def __init__(self, left, right):
        assert isinstance(left, Operator) or isinstance(left, bool) 
        assert isinstance(right, Operator) or isinstance(right, bool)
        BinaryOperator.__init__(self, left, right)
        self._op_sign = '&&'
        self.evaluate += self.__and
        
    def __and(self, context):
        if isinstance(self.l_value, bool): l = self.l_value
        else:  
            self.l_value.evaluate(context)
            l = self.l_value.result
        if isinstance(self.r_value, bool): r = self.r_value
        else:  
            self.r_value.evaluate(context)
            r = self.r_value.result
        self.result = (l and r)

class Or(BinaryOperator):
    '''  represents a logical and between two operators'''
    def __init__(self, left, right):
        assert isinstance(left, Operator) or isinstance(left, bool) 
        assert isinstance(right, Operator) or isinstance(right, bool)
        BinaryOperator.__init__(self, left, right)
        self._op_sign = '||'
        self.evaluate += self.__or
        
    def __or(self, context):
        if isinstance(self.l_value, bool): l = self.l_value
        else:  
            self.l_value.evaluate(context)
            l = self.l_value.result
        if isinstance(self.r_value, bool): r = self.r_value
        else:  
            self.r_value.evaluate(context)
            r = self.r_value.result
        self.result = (l or r)

class UnaryOperator(Operator):
    def __init__(self, arg):
        Operator.__init__(self)
        self.__arg = arg
        self.arg_value = None
        self.evaluate += self.__eval
        
    def __eval(self, context):
        try:
            if operand_type(self.__arg) == Context:
                self.arg_value = access_context_object(self.__arg, context)
            elif isinstance(self.__arg, Operator):
                self.__arg.evaluate(context)
                self.arg_value = self.__arg.result
            else: self.arg_value = self.__arg
        except TypeError:
            log_debug('probably selg__arg is none:' + str(self.__arg), True)

    def __str__(self):
        return '' + self._op_sign + '(' + str(self.__arg) + ')'
    
class Not(UnaryOperator):
    def __init__(self, arg):
        UnaryOperator.__init__(self, arg)
        self._op_sign = '!'
        self.evaluate += self.__not
    
    def __not(self, context):
        if isinstance(self.arg_value, bool): a = self.arg_value
        else: 
            a = self.arg_value.evaluate(context)
            a = self.arg_value.result
        self.result = not a
     
#============================================================================
# 
#============================================================================
class Length(UnaryOperator):
    def __init__(self, arg):
        UnaryOperator.__init__(self, arg)
        self._op_sign = 'len'
        self.evaluate += self.__len
        
    def __len(self, context):
        try:
            self.result = len(self.arg_value)
        except TypeError:
            log_debug('Length operator was applied on wrong type argument' + str(type(self.arg_value)), True)
            self.result = None

class QafAnswerTypeCompatible(UnaryOperator):
    ''' returns the input type that is expected in the top stack qaf instance 
    input_type_variable parameter specifies the location for received inputs, if non then
     standard location is checked'''

    def __init__(self, input_type_variable=None):
        if input_type_variable is not None:
            UnaryOperator.__init__(self, input_type_variable)
        else: 
            UnaryOperator.__init__(self, '@ctx.last_user_utterance.analysed_type')
        self.evaluate += self.__exp
            
    def __exp(self, context):
        try:
            qafi = context[Context.QAF_INSTANCES][-1]
            self.result = input_compatible(qafi.answer_type(), self.arg_value)
        except:
            log_debug('Could not compare the expected answer type with received answer type', exception=True)
            self.result = False

class IsInputType(UnaryOperator): 
    def __init__(self, input_type=InputTypes.NONE):
        UnaryOperator.__init__(self, '@ctx.last_user_utterance.analysed_type')
        self.input_type = input_type
        self.evaluate += self.__exp
            
    def __exp(self, context):
        self.result = (self.arg_value == self.input_type)

class IsAnswerAffirmative(UnaryOperator):
    def __init__(self, input_variable=None):
        if input_variable is not None:
            UnaryOperator.__init__(self, input_variable)
        else:
            UnaryOperator.__init__(self, '@ctx.last_user_utterance.analysed_tokens')
        self.evaluate += self.__exp
            
    def __exp(self, context):
            self.result = trim(self.arg_value) in context[Context.DICTIONARY].AFFIRMATIVE

class IsAnswerNegative(UnaryOperator):
    def __init__(self, input_variable=None):
        if input_variable is not None:
            UnaryOperator.__init__(self, input_variable)
        else:
            UnaryOperator.__init__(self, '@ctx.last_user_utterance.analysed_tokens')
        self.evaluate += self.__exp
            
    def __exp(self, context):
            self.result = trim(self.arg_value) in context[Context.DICTIONARY].NEGATIVE

class ValueOfVariable(UnaryOperator):
    ''' normally Eq can be used to check the value of an operator, or even UnaryOperator'''
    def __init__(self, variable=None):
        UnaryOperator.__init__(self, variable)
        self._op_sign = 'val'
        self.evaluate += self.__exp
            
    def __exp(self, context):
        self.result = self.arg_value
